﻿#region COPYRIGHT© 2005-2013 Phillip Clark. All rights reserved.

// For licensing information see License.txt (MIT style licensing).

#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Text;

namespace FlitBit.Parse
{
	/// <summary>
	/// Generic token structure.
	/// </summary>
	/// <typeparam name="TKind">token kind TKind</typeparam>
	public struct Token<TKind> : IEquatable<Token<TKind>>
		where TKind : struct
	{
// ReSharper disable StaticFieldInGenericType
		static readonly int CHashCodeSeed = typeof(Token<TKind>).AssemblyQualifiedName.GetHashCode();
// ReSharper restore StaticFieldInGenericType

		readonly Capture _capture;
		readonly object _handback;
		readonly TKind _kind;
		readonly TokenSpec _spec;

		/// <summary>
		/// Creates a new instance.
		/// </summary>
		/// <param name="spec"></param>
		/// <param name="kind"></param>
		/// <param name="capture"></param>
		public Token(TokenSpec spec, TKind kind, Capture capture)
		{
			Contract.Requires(capture.IsValid, "capture must be valid");

			_spec = spec;
			_kind = kind;
			_capture = capture;
			_handback = null;
		}

		/// <summary>
		/// Creates a new instance.
		/// </summary>
		/// <param name="spec"></param>
		/// <param name="kind"></param>
		/// <param name="capture"></param>
		/// <param name="handback"></param>
		public Token(TokenSpec spec, TKind kind, Capture capture, object handback)
		{
			Contract.Requires(capture.IsValid, "capture must be valid");

			_spec = spec;
			_kind = kind;
			_capture = capture;
			_handback = handback;
		}

		/// <summary>
		/// Gets the token's capture.
		/// </summary>
		public Capture Capture { get { return _capture; } }
		/// <summary>
		/// Gets the token's handback.
		/// </summary>
		public object Handback { get { return _handback; } }

		/// <summary>
		/// Indicates whether the token is the end of input.
		/// </summary>
		public bool IsEndOfInput { get { return EqualityComparer<TKind>.Default.Equals(_kind, Tokenizer<TKind>.EofKind); } }
		/// <summary>
		/// Indicates whether the token is an error.
		/// </summary>
		public bool IsError { get { return EqualityComparer<TKind>.Default.Equals(_kind, Tokenizer<TKind>.ErrorKind); } }
		/// <summary>
		/// Gets the token's kind.
		/// </summary>
		public TKind Kind { get { return _kind; } }
		/// <summary>
		/// Gets the token's metakind.
		/// </summary>
		public TokenMetaKind MetaKind { get { return _spec.MetaKind; } }
		/// <summary>
		/// Gets the token's specification.
		/// </summary>
		public TokenSpec Spec { get { return _spec; } }

		/// <summary>
		/// Indicates whether this instance and a specified object are equal.
		/// </summary>
		/// <returns>
		/// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
		/// </returns>
		/// <param name="obj">Another object to compare to. </param><filterpriority>2</filterpriority>
		public override bool Equals(object obj)
		{
			return obj is Capture
				&& Equals((Capture) obj);
		}

		/// <summary>
		/// Returns the hash code for this instance.
		/// </summary>
		/// <returns>
		/// A 32-bit signed integer that is the hash code for this instance.
		/// </returns>
		/// <filterpriority>2</filterpriority>
		public override int GetHashCode()
		{
			const int prime = 999067; // a random prime

			var result = CHashCodeSeed * prime;
			if (_spec != null)
			{
				result ^= prime * _spec.GetHashCode();
			}
			result ^= prime * _kind.GetHashCode();
			result ^= prime * _capture.GetHashCode();
			if (_handback != null)
			{
				result ^= prime * _handback.GetHashCode();
			}
			return result;
		}

		/// <summary>
		/// Returns the fully qualified type name of this instance.
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.String"/> containing a fully qualified type name.
		/// </returns>
		/// <filterpriority>2</filterpriority>
		public override string ToString()
		{
			return new StringBuilder(65).Append("{ Capture: ")
																	.Append(_capture)
																	.Append(", Spec: ")
																	.Append(_spec)
																	.Append(", Kind: ")
																	.Append(_kind)
																	.Append(", Handback: ")
																	.Append(_handback)
																	.Append("}")
																	.ToString();
		}

		/// <summary>
		/// Mutates the token by creating a copy and adding a handback.
		/// </summary>
		/// <param name="handback"></param>
		/// <returns></returns>
		public Token<TKind> AddHandback(object handback)
		{
			return new Token<TKind>(Spec, Kind, Capture, handback);
		}

		/// <summary>
		/// Determines if the token spans a line and position.
		/// </summary>
		/// <param name="line"></param>
		/// <param name="linepos"></param>
		/// <returns></returns>
		public bool ContainsPos(int line, int linepos)
		{
			return _capture.ContainsPos(line, linepos);
		}

		/// <summary>
		/// Equality operator; determines if two tokens are egual.
		/// </summary>
		/// <param name="lhs"></param>
		/// <param name="rhs"></param>
		/// <returns></returns>
		public static bool operator ==(Token<TKind> lhs, Token<TKind> rhs)
		{
			return lhs.Equals(rhs);
		}

		/// <summary>
		/// Inequality operator; determines if two tokens are inequal.
		/// </summary>
		/// <param name="lhs"></param>
		/// <param name="rhs"></param>
		/// <returns></returns>
		public static bool operator !=(Token<TKind> lhs, Token<TKind> rhs)
		{
			return !lhs.Equals(rhs);
		}

		#region IEquatable<Token<TKind>> Members

		/// <summary>
		/// Indicates whether the current object is equal to another object of the same type.
		/// </summary>
		/// <returns>
		/// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
		/// </returns>
		/// <param name="other">An object to compare with this object.</param>
		public bool Equals(Token<TKind> other)
		{
			return _spec.Equals(other._spec)
				&& EqualityComparer<TKind>.Default.Equals(_kind, other._kind)
				&& _capture == other._capture
				&& _handback == other._handback;
		}

		#endregion
	}
}